// -*-Igor-*-
// ###################################################################
//  Igor Pro - JEG Image Tools
// 
//  FILE: "JEG Image Profiler"
//                                    created: 11/4/99 {10:09:52 PM} 
//                                last update: 8/5/03 {1:54:28 PM} 
//  Author: Jonathan Guyer
//  E-mail: jguyer@his.com
//    mail: POMODORO no seisan
//     www: http://www.his.com/jguyer/
//  
//========================================================================
//				Copyright (c) 1999-2003 Jonathan Guyer
//========================================================================
//  Permission to use, copy, modify, and distribute this software and its
//  documentation for any purpose and without fee is hereby granted,
//  provided that the above copyright notice appear in all copies and that
//  both that the copyright notice and warranty disclaimer appear in
//  supporting documentation.
//
//  Jonathan Guyer disclaims all warranties with regard to this software,
//  including all implied warranties of merchantability and fitness.  In
//  no event shall Jonathan Guyer be liable for any special, indirect or
//  consequential damages or any damages whatsoever resulting from loss of
//  use, data or profits, whether in an action of contract, negligence or
//  other tortuous action, arising out of or in connection with the use or
//  performance of this software.
//========================================================================
// 
//  Description: 
//      Based loosely on WaveMetrics' "Path Profile" procedures. 
//      
//      Select 
//      
//          Macros->Live Perpendicular Profile 
//      
//      to append a pair of crosshair cursors to the specified image. 
//      Graphs are automatically created alongside the image window to show
//      the profiles as sections of the image.
//
//      The profile scaling is taken from the image. This includes 
//      automatic use of any X/Y waves the image has been plotted against.
//      
//      A dialog will prompt for the image/contour to profile and will
//      ask for wavename bases for the profiles ("_A" or "_B" gets
//      appended to these names). Return an empty name to prevent profiling
//      in that direction. 
//      
//      These procs use the cursor tools, rather than WM's cross-hair 
//      technique. This allows easy use of the keyboard to move the 
//      profiles.
//      
//      If you're uninterested in comparative profiles, simply drag one of
//      the cursors off the image.
//      
//  History
// 
//  modified   by  rev reason
//  ---------- --- --- -----------
//  1999-11-04 JEG 1.0 original
//  2003-08-05 JEG 1.1 Window hooks must return 0 unless they do something.
// ###################################################################
// 

#pragma rtGlobals=2		// Use modern global access method.

#pragma IgorVersion=4

#include "JEG Window Hooks"

Menu "Macros"
	"Live Perpendicular Profile", JEG_Profile_Prompt()
end

// 
// -------------------------------------------------------------------------
// 
// "JEG_Profile_Prompt" --
// 
//  Prompt for image in top graph and for base names of profile waves
// 
// Results:
//  None.
// 
// Side effects:
//  Display profile waves and create automatic links to cursors on image
// -------------------------------------------------------------------------
// 
Proc JEG_Profile_Prompt(imageName, xProfileName, yProfileName)
	String imageName
	Prompt imageName,"Image",popup JEG_Profile_EligibleForList()
	String xProfileName = "profileX"
	Prompt xProfileName, "X Profile (will automatically end with \"_A\" or \"_B\")"
	String yProfileName = "profileY"
	Prompt yProfileName, "Y Profile (will automatically end with \"_A\" or \"_B\")"

	JEG_Profile_Create(imageName,xProfileName,yProfileName)
End

// 
// -------------------------------------------------------------------------
// 
// "JEG_Profile_Display" --
// 
//  Display the specified profile of the specified image in the topmost graph.
// 
// Results:
//  None.
// 
// Side effects:
//  Trace is displayed in a new graph and automatic links are established.
// -------------------------------------------------------------------------
// 
Function JEG_Profile_Display(image,name,yAxisQ,axisLo,axisHi,axisWave,posUnits)
	Wave     image            // Image wave to be profiled
	String   name             // Name of profile wave
	Variable yAxisQ           // Boolean. Y axis or X axis?
	Variable axisLo, axisHi   // Displayed axis range
	Wave     axisWave         // Wave (if any) that defines the coordinates of this image axis
	String   posUnits         // Units for the cursor position display
	
	// Figure out which element of the (artificially) complex position we're
	// interested in (X in real, Y in imaginary). 

	// It's easier to supply the units, rather than determine them here,
	// because they depend on a couple of parameters.
	String posElement

	String axis

	if (yAxisQ)
		axis = "Y"
		posElement = "real"
	else
		axis = "X"
		posElement = "imag"
	endif
	
	String graphName = WinName(0,1)
	
	String df = GetDataFolder(1)
	String graphDF = "root:WinGlobals:" + graphName

	SetDataFolder graphDF
	String/G $("S_JEG_Profile_" + axis + "Path") = df + name
	SVAR profilePath = $("S_JEG_Profile_" + axis + "Path")
	SetDataFolder df
	
	// Shouldn't this be automatically updated as the image changes?
	Variable size = dimsize(image, yAxisQ)
	
	// Automatically distinguish between profiles for different cursors
	Make/O/N=(size) $(profilePath + "_A")
	Make/O/N=(size) $(profilePath + "_B")
	
	GetWindow $graphName, wsize

	// This is only a kludge. Images that occupy the entire screen
	// clearly need something more robust.
	if (yAxisQ)
		// Display the Y profile in a window to the right of the image, as tall
		// as the image, and 200 pixels wide. 
		Display/K=1/W=(V_right+5,V_top,V_right+205,V_bottom)	
	else
		// Display the profile in a window below the image, as wide
		// as the image, and 200 pixels high. 
		Display/K=1/W=(V_left,V_bottom+5,V_right,V_bottom+205)	
	endif
	
	// Store a route to the profile in the image's state variables
	// and a route to the image in the profile's state variables
	SetDataFolder graphDF
	String/G $("S_JEG_Profile_" + axis + "Window") = WinName(0,1)
	NewDataFolder/O/S root:WinGlobals:$WinName(0,1)
	String/G S_JEG_Profile_WindowStr = "root:WinGlobals:" + graphName + ":S_JEG_Profile_" + axis + "Window"
	SetDataFolder df

	// Ensure that appropriate actions are taken when the profile
	// window is closed.
	JEG_WindowHook_Set(WinName(0,1),"JEG_Profile_DestinationHook")
	
	if (WaveExists(axisWave))
		// Image is displayed against an axis wave, so display the profile
		// versus that wave
		
//		// Convert image axis range to point range
//		axisLo = (axisLo - DimOffset(image, yAxisQ))/DimDelta(image, yAxisQ) 
//		axisHi = (axisHi - DimOffset(image, yAxisQ))/DimDelta(image, yAxisQ) 
		
//		// Convert point range to axis wave range
//		axisLo = axisWave[axisLo]
//		axisHi = axisWave[axisHi]

		AppendToGraph/C=(65535,0,0) $(profilePath + "_A") vs axisWave
		AppendToGraph/C=(0,0,65535) $(profilePath + "_B") vs axisWave
	else
		// Image is not displayed against an axis wave, so display the profile
		// with the same scaling as the image wave
		SetScale/P x, DimOffset(image, yAxisQ), DimDelta(image, yAxisQ), WaveUnits(image, yAxisQ), $(profilePath + "_A"), $(profilePath + "_B")

		// Cursor A profile in red, cursor B in blue.
		AppendToGraph/C=(65535,0,0) $(profilePath + "_A")
		AppendToGraph/C=(0,0,65535) $(profilePath + "_B")
	endif
	
	// Match the range of the profile to the range of the image
	SetAxis bottom, axisLo, axisHi
	WaveStats/Q image
	SetAxis left, V_min, V_max
	
	// Make things a little speedier, and get the profile orientated the
	// right way. Yes, I know "orientated" isn't a word.
	// If you get an error, you can safely remove "live = 1"
	ModifyGraph live = 1, swapXY = yAxisQ
	
	// Z scaling
	SetScale d, 0, 0, WaveUnits(image,-1), $(profilePath + "_A"), $(profilePath + "_B")
	
	Label left, " "
	Label bottom, " "
	
	// Create value displays for the current cursor positions and the
	// difference between them.
	
	// We have to use this grotesque string technique because there's no
	// other way to get the ValDisplay to show an arbitrary variable in
	// an arbitrary data folder. 8^P
	
	String s
	String format = "\"%.3W1P" + posUnits + "\""
	
	GetWindow kwTopWin, wsize

	s =  "ValDisplay valDispA format=" + format + ", title=\"A: \", "
	s += "pos = {0,0},"
	s += "size = {100,14},"
	s += "value=(" + posElement + "(" 
	s += "root:WinGlobals:" + graphName 
	s += ":V_JEG_Profile_AScaledPosition))"		
	Execute s

	s =  "ValDisplay valDispB format=" + format + ", title=\"B: \", "
	s += "pos = {" + num2str(V_right - V_left -100) + ",0},"
	s += "size = {100,14},"
	s += "value=(" + posElement + "(" 
	s += "root:WinGlobals:" + graphName 
	s += ":V_JEG_Profile_BScaledPosition))"		
	Execute s
	
	// I don't think that knuckle-draggers can see ''. Screw 'em.
	s =  "ValDisplay valDispD format=" + format + ", title=\": \", "
	s += "pos = {" + num2str((V_right - V_left)/2 -50) + ",15},"
	s += "size = {100,14},"
	s += "value=(" + posElement + "(" 
	s += "root:WinGlobals:" + graphName 
	s += ":V_JEG_Profile_BScaledPosition)"		
	s += "-" + posElement + "(" 
	s += "root:WinGlobals:" + graphName 
	s += ":V_JEG_Profile_AScaledPosition))"		
	Execute s

	ControlBar 32
	
	// Bring the image back in front
	DoWindow/F $graphName	
End

// 
// -------------------------------------------------------------------------
// 
// "JEG_Profile_ScaleFormulaXY" --
// 
//  Construct formula to calculate axis scaling on the fly
// 
// Results:
//  Formula string
// 
// Side effects:
//  None
// -------------------------------------------------------------------------
// 
Function/S JEG_Profile_ScaleFormulaXY(AorB, imageRef, axisWave, yAxisQ)
	String   AorB             // Cursor name
	String   imageRef         // Wave descriptor that'll work in a string
	Wave     axisWave         // Wave (if any) that defines the coordinates of this image axis
	Variable yAxisQ           // Boolean. Y axis or X axis?
	
	// Cursor position is encoded in a complex variable
	String element
	if (yAxisQ)
		element = "imag"
	else
		element = "real"
	endif

	String scaleFormula
	
	if (WaveExists(axisWave))
		// Image is displayed against an axis wave, so use its scaling
		scaleFormula =  GetWavesDataFolder(axisWave,2)
		scaleFormula += "[" + element + "(V_JEG_Profile_" + AorB + "Position)]"
	else
		// No axis wave, so use image's axis scaling
		scaleFormula =  "DimOffset(" + imageRef + ", " + num2istr(yAxisQ) + ")"
		scaleFormula +=   "+" + element + "(V_JEG_Profile_" + AorB + "Position)"
		scaleFormula +=      "* DimDelta(" + imageRef + ", " + num2istr(yAxisQ) + ")"
	endif	
	
	return scaleFormula
End

// 
// -------------------------------------------------------------------------
// 
// "JEG_Profile_ScaleFormulaAB" --
// 
//  Construct formula to calculate cursor scaling on the fly
// 
// Results:
//  Formula string
// 
// Side effects:
//  None
// -------------------------------------------------------------------------
// 
Function/S JEG_Profile_ScaleFormulaAB(AorB, imageRef, xWave, yWave)
	String AorB             // Cursor name
	String imageRef         // Wave descriptor that'll work in a string
	Wave   xWave, yWave     // Waves (if any) that define the coordinates of the image axes

	// Cursor position is encoded in a complex variable

	String scaleFormula = "cmplx("	
	
	scaleFormula += JEG_Profile_ScaleFormulaXY(AorB, imageRef, xWave, 0)
	
	scaleFormula += ","

	scaleFormula += JEG_Profile_ScaleFormulaXY(AorB, imageRef, yWave, 1)

	scaleFormula += ")"

	return scaleFormula
End


// 
// -------------------------------------------------------------------------
// 
// "JEG_Profile_Create" --
// 
//  Do all the hard labor of creating the profile waves, displaying them
//  and making the automagic link to the cursors on the image display.
// 
// Results:
//  None.
// 
// Side effects:
//  Everything.
// -------------------------------------------------------------------------
// 
Function JEG_Profile_Create(imageName,xName,yName)
	string imageName, xName, yName
	
	String graphName = WinName(0,1)
	
	// Create a WinGlobals folder for the profiler state variables
	String df= GetDataFolder(1)
	NewDataFolder/O root:WinGlobals
	NewDataFolder/O/S root:WinGlobals:$graphName
	String graphDF = GetDataFolder(1)
		
	String/G S_CursorAInfo, S_CursorBInfo

	// Absolute and scaled cursor positions
	Variable/G/C V_JEG_Profile_APosition, V_JEG_Profile_BPosition
	Variable/G/C V_JEG_Profile_AScaledPosition, V_JEG_Profile_BScaledPosition
	
	SetDataFolder df
	
	// Obtain pertinant information about the image to profile
	Wave image = ImageNameToWaveRef("", imageName)
	
	String xUnits, yUnits, zUnits
	
	String imageRef = "ImageNameToWaveRef(\"" + graphName + "\",\"" + imageName + "\")"
	
	// Set up strings for automatic assignment of position variables
	// The positions are stored in single, complex variables, with X
	// in the real part and Y in the imaginary part.

	String info = ImageInfo(graphName,imageName,0)
		
	Wave xWave = $(StringByKey("XWAVEDF",info) + StringByKey("XWAVE",info))
	
	if (WaveExists(xWave) > 0)
		// Image is displayed against an X wave, so use its scaling
		xUnits = WaveUnits(xWave,1)
	else
		// No X wave, so use image's X scaling
		xUnits = WaveUnits(image,0)
	endif

	Wave yWave = $(StringByKey("YWAVEDF",info) + StringByKey("YWAVE",info))
	
	if (WaveExists(yWave) > 0)
		// Image is displayed against a Y wave, so use its scaling
		yUnits = WaveUnits(yWave,1)
	else
		// No Y wave, so use image's Y scaling
		yUnits = WaveUnits(image,1)
	endif

	String scaleFormulaA = JEG_Profile_ScaleFormulaAB("A", imageRef, xWave, yWave)
	String scaleFormulaB = JEG_Profile_ScaleFormulaAB("B", imageRef, xWave, yWave)

	// Make sure this variable is assigned in the right place.
	// NVARs don't seem to work.
	SetDataFolder graphDF
	
	// Use formulae from above to automatically update the scaled 
	// cursor positions
	SetFormula V_JEG_Profile_AScaledPosition, scaleFormulaA
	SetFormula V_JEG_Profile_BScaledPosition, scaleFormulaB

	setDataFolder df
	
	// Generate and display the X profile if the user supplied a name
	if (strlen(xName) > 0)
		GetAxis/Q $StringByKey("XAXIS",info)	
		JEG_Profile_Display(image, xName, 0, V_min, V_max, xWave, yUnits)
	endif

	// Generate and display the Y profile if the user supplied a name
	if (strlen(yName) > 0)
		GetAxis/Q $StringByKey("YAXIS",info)	
		JEG_Profile_Display(image, yName, 1, V_min, V_max, yWave, xUnits)
	endif
	
	SetDataFolder graphDF
	
	// Establish automatic updating of profiles
	Variable/G V_JEG_Profile_ATrigger
	Variable/G V_JEG_Profile_BTrigger

	SetFormula V_JEG_Profile_ATrigger, "JEG_Profile_Trigger(root:WinGlobals:" + graphName + ":S_CursorAInfo)"
	SetFormula V_JEG_Profile_BTrigger, "JEG_Profile_Trigger(root:WinGlobals:" + graphName + ":S_CursorBInfo)"
	
	// Refresh the cursor variables
	DoUpdate
	Wave traceA = CsrWaveRef(A, graphName)
	Wave traceB = CsrWaveRef(B, graphName)
	
	setDataFolder df

	// Ensure cleanup is done when the source window is closed
	JEG_WindowHook_Set(graphName,"JEG_Profile_SourceHook")
	
	// If neither cursor is displayed, then show them in default positions.
	// Otherwise, use whatever the user already has up.
	// Either way, we want a red one and a blue one, as full crosshair cursors.
	if (!WaveExists(traceA) && !WaveExists(traceB))
		Variable imageWidth = dimsize(image, 0)
		Variable imageHeight = dimsize(image, 1)
	
		Cursor/I/P/A=1/H=1/S=2/C=(65535,0,0) A, $imageName imageWidth/4,imageHeight/4
		Cursor/I/P/A=1/H=1/S=2/C=(0,0,65535) B, $imageName imageWidth/2,imageHeight/2
	else
		Cursor/M/H=1/S=2/C=(65535,0,0) A
		Cursor/M/H=1/S=2/C=(0,0,65535) B
	endif
End

// 
// -------------------------------------------------------------------------
// 
// "JEG_Profile_Trigger" --
// 
//  Called whenever associated cursor changes.
// 
// Results:
//  None
// 
// Side effects:
//  Updates profile waves
// -------------------------------------------------------------------------
// 
Function JEG_Profile_Trigger(cursorInfo)
	String cursorInfo
	
	String graphName = StringByKey("GRAPH", cursorInfo)	
	
	// The cursor's not on a graph?!?
	if (strlen(graphName) <= 0)
		return 0
	endif
	
	// Extract cursor information
	String cursorName  = StringByKey("CURSOR", cursorInfo)
	Variable xPosition = NumberByKey("POINT", cursorInfo)
	Variable yPosition = NumberByKey("YPOINT", cursorInfo)

	// What a PITA
	if (cmpstr(cursorName, "A") == 0)
		Wave image = CsrWaveRef(A, graphName)
	else
		Wave image = CsrWaveRef(B, graphName)
	endif
	
	String df= GetDataFolder(1)
	SetDataFolder "root:WinGlobals:" + graphName
	
	// Store the new cursor position, encoded in a complex variable
	NVAR/C profilePosition = $("V_JEG_Profile_" + cursorName + "Position")
	
	profilePosition = cmplx(xPosition,yPosition)

  // If a profile has been defined, update it
	SVAR xProfilePath = S_JEG_Profile_XPath
	
	if (SVAR_Exists(xProfilePath) && (strlen(xProfilePath) > 0))
		Wave xProfile = $(xProfilePath + "_" + cursorName)
		
		if (WaveExists(image) > 0)
			// Set the profile to the appropriate slice of the image
			xProfile = image[p][yPosition]
		else
			// The cursor's not on a wave, so make the profile disappear
			xProfile = NaN
		endif
	endif

  // If a profile has been defined, update it
	SVAR yProfilePath = S_JEG_Profile_YPath

	if  (SVAR_Exists(yProfilePath) && (strlen(yProfilePath) > 0))
		Wave yProfile = $(yProfilePath + "_" + cursorName)
	
		if (WaveExists(image) > 0)
			// Set the profile to the appropriate slice of the image
			yProfile = image[xPosition][p]
		else
			// The cursor's not on a wave, so make the profile disappear
			yProfile = NaN
		endif
	endif
	
	SetDataFolder df
End

// 
// -------------------------------------------------------------------------
// 
// "JEG_Profile_EligibleForList" --
// 
//  Determine the images and countours in the current graph
// 
// Results:
//  Formatted list of eligible items
// 
// Side effects:
//  None
// -------------------------------------------------------------------------
// 
Function/S JEG_Profile_EligibleForList()

	String theList
	
	String imageList = ImageNameList("",";")
	
	theList = imageList
	
	String contourList = ContourNameList("",";")
	
	// If we've got images _and_ contours, put in a separator
	if ((strlen(theList) > 0) %& (strlen(contourList) > 0))
		theList += "-;"
	endif
	theList += contourList
	
	return theList
End

// 
// -------------------------------------------------------------------------
// 
// "JEG_Profile_SourceHook" --
// 
//  Take automatic actions when a profile source window shuts
// 
// Results:
//  None
// 
// Side effects:
//  Profile windows and all the profile state variables are killed
// -------------------------------------------------------------------------
// 
Function JEG_Profile_SourceHook(infoStr)
	String infoStr
	
	String window = StringByKey("WINDOW", infoStr)
	String event  = StringByKey("EVENT", infoStr)
	
	if (cmpstr(event, "kill") == 0)
		String df= GetDataFolder(1)
		NewDataFolder/O root:WinGlobals
		NewDataFolder/O/S root:WinGlobals:$window
		
		KillVariables/Z V_JEG_Profile_ATrigger,  V_JEG_Profile_BTrigger
		KillVariables/Z V_JEG_Profile_APosition, V_JEG_Profile_BPosition
		KillVariables/Z V_JEG_Profile_AScaledPosition, V_JEG_Profile_BScaledPosition
		
		KillStrings/Z S_JEG_Profile_XPath, S_JEG_Profile_YPath

		SVAR profileXWindow = S_JEG_Profile_XWindow
		if (SVAR_Exists(profileXWindow))
			DoWindow/K $profileXWindow
			KillStrings/Z S_JEG_Profile_XWindow
		endif
		
		SVAR profileYWindow = S_JEG_Profile_YWindow
		if (SVAR_Exists(profileYWindow))
			DoWindow/K $profileYWindow
			KillStrings/Z S_JEG_Profile_YWindow
		endif
		
		SetDataFolder df
		
		return 1
	endif
	
	return 0
End

// 
// -------------------------------------------------------------------------
// 
// "JEG_Profile_DestinationHook" --
// 
//  Take automatic action when a profile display window is closed
// 
// Results:
//  None
// 
// Side effects:
//  Kill state variables that link this profile to the original image
// -------------------------------------------------------------------------
// 
Function JEG_Profile_DestinationHook(infoStr)
	String infoStr
	
	String window = StringByKey("WINDOW", infoStr)
	String event  = StringByKey("EVENT", infoStr)
	
	if (cmpstr(event, "kill") == 0)
		String df= GetDataFolder(1)
		NewDataFolder/O root:WinGlobals
		NewDataFolder/O/S root:WinGlobals:$window
				
		SVAR profileWindowStr = S_JEG_Profile_WindowStr
		KillStrings/Z $profileWindowStr
		KillStrings/Z S_JEG_Profile_WindowStr
		
		SetDataFolder df
		
		return 1
	endif
	
	return 0
End

